﻿using System;
using System.Collections.Concurrent;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Xml.Linq;
using Tyche.Common.Util.Models.Entity;

namespace Tyche.Common.Util.Utils
{
    public class DynamicMethodUtil
    {
        /// <summary>
        /// 动态方法字典
        /// </summary>
        private static ConcurrentDictionary<string, DynamicMethodEntity> dynamicMethods = new ConcurrentDictionary<string, DynamicMethodEntity>();

        /// <summary>
        /// 执行动态方法
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="xml"></param>
        /// <param name="args"></param>
        /// <returns></returns>
        public static T Run<T>(string xml, params object[] args)
        {
            var document = XDocument.Parse(xml);
            var assemblyNode = document.Element("assembly");
            var expires = DateTime.Now.AddSeconds(Convert.ToInt32(assemblyNode.Attribute("expires").Value));
            var moduleNode = assemblyNode.Element("module");
            var typeNode = moduleNode.Element("type");
            var methodNode = typeNode.Element("method");

            if (assemblyNode == null)
            {
                throw new Exception("缺少assembly标签");
            }
            else if (expires == null)
            {
                throw new Exception("缺少expires属性");
            }
            else if (moduleNode == null)
            {
                throw new Exception("缺少moduleNode标签");
            }
            else if (typeNode == null)
            {
                throw new Exception("缺少typeNode标签");
            }
            else if (methodNode == null)
            {
                throw new Exception("缺少methodNode标签");
            }

            var fullName = string.Format("{0}.{1}.{2}.{3}"
                , assemblyNode.Attribute("name").Value
                , moduleNode.Attribute("name").Value
                , typeNode.Attribute("name").Value
                , methodNode.Attribute("name").Value);

            if (!dynamicMethods.ContainsKey(fullName) || dynamicMethods[fullName].Expires < DateTime.Now)
            {
                dynamicMethods[fullName] = BuildDynamicMethod(assemblyNode, expires, moduleNode, typeNode, methodNode);
            }

            return (T)dynamicMethods[fullName].Method.Invoke(null, args);
        }

        /// <summary>
        /// 构建动态方法
        /// </summary>
        /// <param name="assemblyNode"></param>
        /// <param name="expires"></param>
        /// <param name="moduleNode"></param>
        /// <param name="typeNode"></param>
        /// <param name="methodNode"></param>
        /// <returns></returns>
        private static DynamicMethodEntity BuildDynamicMethod(XElement assemblyNode, DateTime expires, XElement moduleNode, XElement typeNode, XElement methodNode)
        {
            var defineLabelNodes = methodNode.Elements("defineLabel");
            var declareLocalNodes = methodNode.Elements("declareLocal");
            var emitNodes = methodNode.Elements("emit");

            var assemblyBuilder = AppDomain
               .CurrentDomain
               .DefineDynamicAssembly(new AssemblyName(assemblyNode.Attribute("name").Value), AssemblyBuilderAccess.RunAndCollect);

            var moduleBuilder = assemblyBuilder.DefineDynamicModule(moduleNode.Attribute("name").Value);

            var typeBuilder = moduleBuilder.DefineType(typeNode.Attribute("name").Value, TypeAttributes.Public);

            var method = typeBuilder.DefineMethod(methodNode.Attribute("name").Value
                , MethodAttributes.Public | MethodAttributes.Static
                , Type.GetType(methodNode.Attribute("returnType").Value)
                , methodNode.Attribute("parameterTypes").Value.Split(',').Select(parameter => Type.GetType(parameter)).ToArray());

            var ilGenerator = method.GetILGenerator();

            var labels = defineLabelNodes.ToDictionary(
                label => label.Attribute("name").Value, label => ilGenerator.DefineLabel());

            var locals = declareLocalNodes.ToDictionary(
                local => local.Attribute("name").Value, local => ilGenerator.DeclareLocal(Type.GetType(local.Attribute("type").Value)));

            foreach (var emit in emitNodes)
            {
                var opcode = emit.Attribute("opcode").Value;

                if (opcode == "Ldc_I4")
                {
                    ilGenerator.Emit(OpCodes.Ldc_I4, Convert.ToInt32(emit.Attribute("arg").Value));
                }
                else if (opcode == "Newobj")
                {
                    ilGenerator.Emit(OpCodes.Newobj, Type.GetType(emit.Attribute("type").Value)
                        .GetConstructor(emit.Attribute("parameterTypes").Value.Split(',').Select(parameterType => Type.GetType(parameterType)).ToArray()));
                }
                else if (opcode == "Stloc")
                {
                    ilGenerator.Emit(OpCodes.Stloc, locals[emit.Attribute("local").Value]);
                }
                else if (opcode == "Ldloc")
                {
                    ilGenerator.Emit(OpCodes.Ldloc, locals[emit.Attribute("local").Value]);
                }
                else if (opcode == "Ldarg")
                {
                    ilGenerator.Emit(OpCodes.Ldarg, Convert.ToInt32(emit.Attribute("arg").Value));
                }
                else if (opcode == "Ceq")
                {
                    ilGenerator.Emit(OpCodes.Ceq);
                }
                else if (opcode == "Brfalse")
                {
                    ilGenerator.Emit(OpCodes.Brfalse, labels[emit.Attribute("label").Value]);
                }
                else if (opcode == "Beq")
                {
                    ilGenerator.Emit(OpCodes.Beq, labels[emit.Attribute("label").Value]);
                }
                else if (opcode == "Br")
                {
                    ilGenerator.Emit(OpCodes.Br, labels[emit.Attribute("label").Value]);
                }
                else if (opcode == "Ret")
                {
                    ilGenerator.Emit(OpCodes.Ret);
                }
                else if (opcode == "MarkLabel")
                {
                    ilGenerator.MarkLabel(labels[emit.Attribute("label").Value]);
                }
                else if (opcode == "Call")
                {
                    ilGenerator.Emit(OpCodes.Call, Type.GetType(emit.Attribute("type").Value)
                        .GetMethod(emit.Attribute("method").Value, emit.Attribute("parameterTypes").Value.Split(',').Select(parameterType => Type.GetType(parameterType)).ToArray()));
                }
            }

            return new DynamicMethodEntity
            {
                Expires = expires,
                Method = typeBuilder.CreateType().GetMethod(methodNode.Attribute("name").Value, methodNode.Attribute("parameterTypes").Value.Split(',').Select(
                    parameter => Type.GetType(parameter)).ToArray())
            };
        }
    }
}
